#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

#include "config.h"
#include "libxsp.h"

#include "psd_conn.h"
#include "psd_modules.h"
#include "psd_measurement.h"
#include "psd_conn_tcp.h"
#include "psd_conn_file.h"
#include "psd_config.h"
#include "psd_settings.h"
#include "psd_protocols.h"
#include "psd_logger.h"
#include "psd_tpool.h"

#include "perfsonar_ls_client.h"

#include "compat.h"

int psd_ls_init();
static void *psd_registration_main_loop(void *arg);
static int psd_registration_construct_metadata(char *buf, int buflen);

static int *sockets;
static int num_sockets;

static psdModule psd_ls_module = {
	.desc = "Lookup Service Registration Module",
	.dependencies = "",
	.init = psd_ls_init
};

psdModule *module_info() {
	return &psd_ls_module;
}

static char **ls_instances = NULL;
static int ls_instance_count = 0;
static int ls_registration_interval = 0;
static char **ips = NULL;
static int ip_count = 0;
static int udt_port = 0;
static int udt_mss = 0;
static int tcp_port = 0;
static int tcp_recvbuf = 0;
static int tcp_sendbuf = 0;
static char *psd_service_type = "PD";
static char *psd_service_name = NULL;
static char *psd_service_description = NULL;

int psd_ls_init() {
	char **protocols;
	int num_protocols;
	psdSettings *settings;
	int i;

	if (psd_depot_settings_get_list("lookup_service", "registration_instances", &ls_instances, &ls_instance_count) != 0) {
		psd_err(0, "No lookup service instances to register with");
		goto error_exit;
	}

	if (psd_depot_settings_get_int("lookup_service", "registration_interval", &ls_registration_interval) != 0) {
		psd_warn(0, "No registration interval specified. Defaulting to once an hour");
		ls_registration_interval = 3600;
	} else {
		psd_warn(0, "Lookup service registration interval: %d minutes", ls_registration_interval);
		ls_registration_interval *= 60;
	}


	if (get_ips(&ips, &ip_count) != 0) {
		psd_err(0, "Couldn't lookup list of IPs");
		goto error_exit;
	}

	// grab the protocol parameters
	protocols = psd_get_protocol_list(&num_protocols);
	if (!protocols) {
		psd_err(0, "No protocols loaded");
		goto error_exit;
	}

	if (psd_depot_settings_get_section("listeners", &settings) != 0) {
		settings = psd_settings_alloc();
	}

	for(i = 0; i < num_protocols; i++) {
		int disabled = 0;

		if (psd_settings_get_bool_2(settings, protocols[i], "disabled", &disabled) != 0) {
			disabled = 0;
		}

		if (disabled)
			continue;

		if (strcmp(protocols[i], "tcp") == 0) {
			if (psd_settings_get_int_2(settings, "tcp", "port", &tcp_port) != 0) {
				tcp_port = 5006;
			}

			if (psd_settings_get_int_2(settings, "tcp", "recv_bufsize", &tcp_recvbuf) != 0) {
				tcp_recvbuf = 0;
				}

			if (psd_settings_get_int_2(settings, "tcp", "send_bufsize", &tcp_sendbuf) != 0) {
				tcp_sendbuf = 0;
			}
		} else if (strcmp(protocols[i], "udt") == 0) {
			if (psd_settings_get_int_2(settings, "udt", "port", &udt_port) != 0) {
				udt_port = 5006;
			}

			if (psd_settings_get_int_2(settings, "udt", "mss", &udt_mss) != 0) {
				udt_mss = 0;
			}
		}
	}

	strlist_free(protocols, num_protocols);
	psd_settings_free(settings);

	if (psd_tpool_exec(psd_registration_main_loop, NULL) != 0) {
		psd_err(0, "couldn't execute socket handler thread");
		goto error_exit;
	}

	return 0;

error_exit:
	return -1;
}

void *psd_registration_main_loop(void *arg) {
	char *ls_key = NULL;
	perfSONARService *service;
	int num_options;
	char metadata[8192];
	char *md[] = { metadata };
	int addr_num;

	service = perfsonar_service_alloc(SERVICE_NONPERFSONAR);
	perfsonar_service_set_type(service, service->type);
	perfsonar_service_set_name(service, service->name);
	perfsonar_service_set_description(service, service->description);

	perfsonar_service_set_prefix(service, "nmtb");
	perfsonar_service_set_namespace(service, "http://ogf.org/schema/network/base/20070707/");

	addr_num = 0;
	if (udt_port > 0) {
		int i;

		for(i = 0; i < ip_count; i++) {
			char buf[255];
			if (strchr(ips[i], ':') != NULL) {
				snprintf(buf, sizeof(buf), "udt://[%s]:%d", ips[i], udt_port);
			} else {
				snprintf(buf, sizeof(buf), "udt://%s:%d", ips[i], udt_port);
			}

			perfsonar_service_add_address(service, "uri", buf);
		}
	}

	if (tcp_port > 0) {
		int i;

		for(i = 0; i < ip_count; i++) {
			char buf[255];

			if (strchr(ips[i], ':') != NULL) {
				snprintf(buf, sizeof(buf), "tcp://[%s]:%d", ips[i], tcp_port);
			} else {
				snprintf(buf, sizeof(buf), "tcp://%s:%d", ips[i], tcp_port);
			}

			perfsonar_service_add_address(service, "uri", buf);
		}
	}

	metadata[0] = '\0';
	psd_registration_construct_metadata(metadata, sizeof(metadata));

	while(1) {
		int i;
		char buf[8192];

		if (ls_key) {
			if (perfsonar_ls_keepalive(ls_instances[0], ls_key) != 0) {
				ls_key = NULL;
				psd_err(0, "LS Keepalive failed");
			} else {
				psd_info(5, "Keepalive of data with key %s succeeded", ls_key);
			}
		} else {
			if (perfsonar_ls_register(ls_instances[0], service, md, 1, &ls_key) != 0) {
				ls_key = NULL;
				psd_err(0, "LS Registration failed");
			} else {
				psd_info(5, "Registered data with key %s", ls_key);
			}
		}

		sleep(ls_registration_interval);
	}

	return NULL;
}

int psd_registration_construct_metadata(char *buf, int buflen) {
	int i;

	strlcat(buf, "<nmwg:metadata xmlns:nmwg=\"http://ggf.org/ns/nmwg/base/2.0/\">", buflen);
	strlcat(buf, "<nmwg:subject>", buflen);
	strlcat(buf, "<nmtb:node xmlns:nmtb=\"http://ogf.org/schema/network/topology/base/20070707/\">", buflen);

	if (udt_port > 0) {
		strlcat(buf, "<nmtudt:port xmlns:nmtudt=\"http://ogf.org/schema/network/topology/l4/20070707/udt/20070707/\">", buflen);
		strlfcat(buf, buflen, "<nmtudt:portNum>%d</nmtudt:portNum>", udt_port);
		if (udt_mss > 0) {
			strlfcat(buf, buflen, "<nmtudt:mtu>%d</nmtudt:mtu>", udt_mss);
		}
		strlcat(buf, "</nmtudt:port>", buflen);
	}

	if (tcp_port > 0) {
		strlcat(buf, "<nmttcp:port xmlns:nmttcp=\"http://ogf.org/schema/network/topology/l4/20070707/tcp/20070707/\">", buflen);
		strlfcat(buf, buflen, "<nmttcp:portNum>%d</nmttcp:portNum>", tcp_port);
		if (tcp_recvbuf > 0) {
			strlfcat(buf, buflen, "<nmttcp:recvBufferSize>%d</nmttcp:recvBufferSize>", tcp_recvbuf);
		}
		if (tcp_sendbuf > 0) {
			strlfcat(buf, buflen, "<nmttcp:sendBufferSize>%d</nmttcp:sendBufferSize>", tcp_sendbuf);
		}
		strlcat(buf, "</nmttcp:port>", buflen);
	}

	for(i = 0; i < ip_count; i++) {
		char *addr_type;

		if (strchr(ips[i], ':') != NULL) {
			addr_type = "ipv6";
		} else if (strchr(ips[i], '.') != NULL) {
			addr_type = "ipv4";
		} else {
			addr_type = "logical";
		}

		strlcat(buf, "<nmtl3:port xmlns:nmtl3=\"http://ogf.org/schema/network/topology/l3/20070707/\">", buflen);
		strlfcat(buf, buflen, "<nmtl3:address type=\"%s\">%s</nmtl3:address>", addr_type, ips[i]);
		strlcat(buf, "</nmtl3:port>", buflen);
	}

	strlcat(buf, "</nmtb:node>", buflen);
	strlcat(buf, "</nmwg:subject>", buflen);
	strlcat(buf, "<nmwg:eventType>http://ggf.org/ns/nmwg/tools/phoebus/1.0</nmwg:eventType>", buflen);
	strlcat(buf, "</nmwg:metadata>", buflen);
}
